package echo.netty.TestProtocol;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;

/**
 * @author： zhangnan
 * @date： 2025/5/12 14:09
 * @description： 自定义协议 消息 编解码 器
 * ByteToMessageCodec后边的泛型是 你消息的映射的消息类型
 * 继承自ByteToMessageCodec的自定义编解码类是非线程安全的，不能使用 @ChannelHandler.Sharable 注解标注
 * @modifiedBy：
 * @version: 1.0
 */
@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {

    /**
     * 自定义协议要素
     * 1. 魔数，用来在第一时间判定是否是无效数据包
     * 2. 版本号，可以支持协议的升级
     * 3. 序列化算法，消息正文到底采用哪种序列化反序列化方式，可以由此扩展，例如:json、protobufhessian、jdk
     * 4. 指令类型，是登录、注册、单聊、群聊.. 跟业务相关
     * 5. 请求序号，为了双工通信，提供异步能力
     * 6. 正文长度
     * 7. 消息正文
     */

    // 编码 - 出栈前把自定义的消息编码成 ByteBuf
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        // 1. 四个字节 魔数
        out.writeBytes(new byte[]{1, 2, 3, 4});
        // 2. 1个字节版本号
        out.writeByte(1);
        // 3. 1个字节的序列化方式 jdk：0；json：1
        out.writeByte(0); // 使用JDK的序列化方式
        // 4. 1个字节的指令类型
        out.writeByte(msg.getMessageType());
        // 5. 4个字节的请求序号
        out.writeInt(msg.getSequenceId());
        // 一般定义协议时(除去消息内容)，都会定义为2的整数倍，
        // 这个自定义协议的长度为15，所以再加上一个字节，使其满足习惯，
        // 该字节为 对其填充，没有意义
        out.writeByte(0xff);
        // 6. 4个字节的正文长度
        // 6.1 把对象转换成二进制的字节数组
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(msg);
        byte[] bytes = bos.toByteArray();
        // 6.2 获取正文长度
        out.writeInt(bytes.length);

        // 7. 消息内容
        out.writeBytes(bytes);
    }

    // 解码 - 入栈时把ByteBuf解码成自定义协议的消息
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        int magicNum = in.readInt(); // 魔数
        byte version = in.readByte(); // 版本号
        byte serializerType = in.readByte(); // 序列化方式
        byte messageType = in.readByte(); // 指令类型
        int sequenceId = in.readInt(); // 请求序号
        in.readByte(); // 占位符，无意义
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Message message = (Message) ois.readObject();
        log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
        log.debug("{}", message);
        // 解析后的消息需要加到 out 里，否则后续的 handler 是无法拿到消息的
        out.add(message);
    }
}
